// SPDX-License-Identifier: GPL-2.0
/*
 * dram.c - Lombo dram-thermal driver module
 *
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/thermal.h>

#include <mach/debug.h>

struct lombo_dram {
	int t_refi;
	int t_ras_max;
	unsigned int cool_state;
	void __iomem *base;
	struct thermal_cooling_device *d_cdev;
	struct mutex d_mutex;
};

static int lombo_dram_get_max_state(struct thermal_cooling_device *cdev,
					unsigned long *state)
{
	if (!state)
		return -EINVAL;

	*state = 2;

	return 0;
}

static int lombo_dram_get_cur_state(struct thermal_cooling_device *cdev,
					unsigned long *state)
{
	struct lombo_dram *l_dram = cdev->devdata;

	*state = l_dram->cool_state;

	return 0;
}

static int lombo_dram_set_cur_state(struct thermal_cooling_device *cdev,
					unsigned long state)
{
	struct lombo_dram *l_dram = cdev->devdata;
	int ret = 0;
	int t_refi, t_ras_max, reg_val;

	if (l_dram->cool_state == state)
		return 0;

	l_dram->cool_state = state;

	mutex_lock(&l_dram->d_mutex);

	if (state == 2) {
		t_ras_max = l_dram->t_ras_max / 4;
		t_refi = l_dram->t_refi / 4;
	} else if (state == 1) {
		t_ras_max = l_dram->t_ras_max / 2;
		t_refi = l_dram->t_refi / 2;
	} else if (state == 0) {
		t_ras_max = l_dram->t_ras_max;
		t_refi = l_dram->t_refi;
	} else {
		ret = -EINVAL;
		PRT_ERR("error state\n");
		goto out;
	}

	reg_val = (readl(l_dram->base + 0x44) & ~(0xfff << 16)) | (t_refi << 16);
	writel(reg_val, l_dram->base + 0x44);

	reg_val = readl(l_dram->base + 0x3c) & (~(1 << 1));
	writel(reg_val, l_dram->base + 0x3c);

	reg_val = readl(l_dram->base + 0x3c) | (1 << 1);
	writel(reg_val, l_dram->base + 0x3c);

	reg_val = (readl(l_dram->base + 0x70) & ~(0x7f << 8)) | (t_ras_max << 8);
	writel(0, l_dram->base + 0x1a0);
	writel(reg_val, l_dram->base + 0x70);
	writel(1, l_dram->base + 0x1a0);

	do {
		reg_val = readl(l_dram->base + 0x1a4) & 0x1;
	} while (reg_val != 1);

out:
	mutex_unlock(&l_dram->d_mutex);

	return ret;
}

static struct thermal_cooling_device_ops const dram_cooling_ops = {
	.get_max_state = lombo_dram_get_max_state,
	.get_cur_state = lombo_dram_get_cur_state,
	.set_cur_state = lombo_dram_set_cur_state,
};

static int lombo_dram_probe(struct platform_device *pdev)
{
	struct resource *res;
	struct lombo_dram *l_dram;
	struct device_node *np = pdev->dev.of_node;
	int ret = 0;
	int reg_val;
	char cdev_name[THERMAL_NAME_LENGTH];

	l_dram = kzalloc(sizeof(struct lombo_dram), GFP_KERNEL);
	if (l_dram == NULL)
		return -EINVAL;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		PRT_ERR("%s get resources fail\n", __func__);
		return -EINVAL;
	}

	l_dram->base =  ioremap(res->start, resource_size(res));
	if (IS_ERR(l_dram->base)) {
		dev_err(&pdev->dev, "err ioremap invalid\n");
		return PTR_ERR(l_dram->base);
	}

	reg_val = (readl(l_dram->base) & 0x1);
	if (reg_val != 1) {
		PRT_INFO("Dram type do not supporting being cooling device\n");
		goto err;
	}

	snprintf(cdev_name, sizeof(cdev_name), "thermal-dram");
	l_dram->d_cdev = thermal_of_cooling_device_register(np, cdev_name,
							l_dram,
							&dram_cooling_ops);
	if (IS_ERR(l_dram->d_cdev)) {
		PRT_ERR("register cooling device %s error\n", cdev_name);
		goto err;
	}
	l_dram->cool_state = 0;

	mutex_init(&l_dram->d_mutex);
	mutex_lock(&l_dram->d_mutex);
	l_dram->t_refi = (readl(l_dram->base + 0x44) >> 16) & 0xfff;
	l_dram->t_ras_max = (readl(l_dram->base + 0x70) >> 8) & 0x7f;
	mutex_unlock(&l_dram->d_mutex);
	platform_set_drvdata(pdev, (void *)l_dram);
	PRT_INFO("lombo_dram probe success\n");
	goto out;

err:
	kfree(l_dram);
out:
	return ret;
}


static int lombo_dram_remove(struct platform_device *pdev)
{
	struct lombo_dram *l_dram = platform_get_drvdata(pdev);

	thermal_cooling_device_unregister(l_dram->d_cdev);
	platform_set_drvdata(pdev, NULL);
	mutex_destroy(&l_dram->d_mutex);
	kfree(l_dram);

	return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id lombo_dram_dt_match[] = {
	{ .compatible = "lombo-dram" },
};
MODULE_DEVICE_TABLE(of, lombo_dram_dt_match);
#endif

static struct platform_driver lombo_dram_driver = {
	.probe		= lombo_dram_probe,
	.remove		= lombo_dram_remove,
	.driver		= {
		.name	= "lombo-dram",
		.owner	= THIS_MODULE,
	#ifdef CONFIG_OF
		.of_match_table = of_match_ptr(lombo_dram_dt_match),
	#endif
	},
};

static int __init lombo_dram_driver_init(void)
{
	return platform_driver_register(&lombo_dram_driver);
}
module_init(lombo_dram_driver_init);

static void __exit lombo_dram_driver_exit(void)
{
	platform_driver_unregister(&lombo_dram_driver);
}
module_exit(lombo_dram_driver_exit);

MODULE_DESCRIPTION("LomboTech gpadc driver");
MODULE_AUTHOR("lomboswer@lombotech.com");
MODULE_LICENSE("GPL");
